#include "gdmodelquerymaker.h"

#include "../editors/ieditoritem.h"
#include "../graphs/iedgedata.h"

#include <QtCore/QQueue>

using namespace core;

typedef QHash<QString, QList<IGraphicEditorItem* > > NameHash;
typedef QList<IGraphicEditorItem* > Items;

GDModelQueryMaker::GDModelQueryMaker(const GraphicDataModel& model) :
    m_model(model)
{

}

GDModelQueryMaker::~GDModelQueryMaker()
{

}

QList<int> GDModelQueryMaker::getTypes() const
{
    QList<int> ret = m_model.m_categorizedItems.keys();

    return ret;
}

IGraphicEditorItem* GDModelQueryMaker::getItem(const QUuid& id) const
{
    IGraphicEditorItem* ret = 0;

    if (m_model.m_items.contains(id))
    {
        ret = m_model.m_items[id];
    }

    return ret;
}

QList<IGraphicEditorItem* > GDModelQueryMaker::getItems() const
{
    QList<IGraphicEditorItem* > ret = m_model.m_items.values();

    return ret;
}

QList<IGraphicEditorItem* > GDModelQueryMaker::getItems(
    const QString& name) const
{
    QList<IGraphicEditorItem* > ret;

    foreach (const NameHash& items, m_model.m_categorizedItems.values())
    {
        if (items.contains(name))
        {
            ret.append(items[name]);
        }
    }

    return ret;
}

QList<IGraphicEditorItem* > GDModelQueryMaker::getItems(int type,
    const QString& name) const
{
    QList<IGraphicEditorItem* > ret;

    if (m_model.m_categorizedItems.contains(type))
    {
        if (!name.isEmpty())
        {
            if (m_model.m_categorizedItems[type].contains(name))
            {
                ret.append(m_model.m_categorizedItems[type][name]);
            }
        }
        else
        {
            NameHash items = m_model.m_categorizedItems[type];

            foreach (const Items& _items, items.values())
            {
                ret.append(_items);
            }
        }
    }

    return ret;
}

QList<const ConnectionData* > GDModelQueryMaker::getConnections(
    const QUuid& id) const
{
    QList<const ConnectionData* > ret;

    if (m_model.m_items.contains(id))
    {
        IGraphicEditorItem* item = m_model.m_items[id];

        if (m_model.m_nodes.contains(item))
        {
            Node* node = m_model.m_nodes[item];

            const QSet<Node* > incoming = node->getIncomingConnections();
            const QSet<Node* > outgoing = node->getOutgoingConnections();

            foreach (Node* from, incoming)
            {
                Edge* edge = m_model.m_graph.getEdge(from, node);

                Q_ASSERT (edge != 0); // can't be in incoming if edge is null

                const ConnectionData* data =
                    dynamic_cast<const ConnectionData* >(edge->getData());

                Q_ASSERT (data != 0); // can't be null, out of sync

                ret.append(data);
            }

            foreach (Node* to, outgoing)
            {
                Edge* edge = m_model.m_graph.getEdge(node, to);

                Q_ASSERT (edge != 0); // can't be in incoming if edge is null

                const ConnectionData* data =
                    dynamic_cast<const ConnectionData* >(edge->getData());

                Q_ASSERT (data != 0); // can't be null, out of sync

                ret.append(data);
            }
        }
    }

    return ret;
}

QList<const ConnectionData* > GDModelQueryMaker::getIncomingConnections(
    const QUuid& id) const
{
    QList<const ConnectionData* > ret;

    if (m_model.m_items.contains(id))
    {
        IGraphicEditorItem* item = m_model.m_items[id];

        if (m_model.m_nodes.contains(item))
        {
            Node* node = m_model.m_nodes[item];

            const QSet<Node* > incoming = node->getIncomingConnections();

            foreach (Node* from, incoming)
            {
                Edge* edge = m_model.m_graph.getEdge(from, node);

                Q_ASSERT (edge != 0); // can't be in incoming if edge is null

                const ConnectionData* data =
                    dynamic_cast<const ConnectionData* >(edge->getData());

                Q_ASSERT (data != 0); // can't be null, out of sync

                ret.append(data);
            }
        }
    }

    return ret;
}

QList<const ConnectionData* > GDModelQueryMaker::getOutgoingConnections(
    const QUuid& id) const
{
    QList<const ConnectionData* > ret;

    if (m_model.m_items.contains(id))
    {
        IGraphicEditorItem* item = m_model.m_items[id];

        if (m_model.m_nodes.contains(item))
        {
            Node* node = m_model.m_nodes[item];

            const QSet<Node* > outgoing = node->getOutgoingConnections();

            foreach (Node* to, outgoing)
            {
                Edge* edge = m_model.m_graph.getEdge(node, to);

                Q_ASSERT (edge != 0); // can't be in incoming if edge is null

                const ConnectionData* data =
                    dynamic_cast<const ConnectionData* >(edge->getData());

                Q_ASSERT (data != 0); // can't be null, out of sync

                ret.append(data);
            }
        }
    }

    return ret;
}

bool GDModelQueryMaker::canFindPath(IGraphicEditorItem* startPoint,
    IGraphicEditorItem* endPoint) const
{
    bool ret = false;

    if (m_model.m_items.contains(startPoint->id()) &&
        m_model.m_items.contains(endPoint->id()))
    {
        if (m_model.m_nodes.contains(startPoint) &&
            m_model.m_nodes.contains(endPoint))
        {
            Node* startNode = m_model.m_nodes[startPoint];
            Node* endNode = m_model.m_nodes[endPoint];

            // BFS
            QQueue<Node* > queue;
            QSet<Node* > visited;

            bool found = false;

            queue.enqueue(startNode);

            while (!queue.isEmpty())
            {
                Node* current = queue.dequeue();
                visited.insert(current);

                const QSet<Node* > outgoing =
                    current->getOutgoingConnections();

                foreach (Node* to, outgoing)
                {
                    if (to == endNode)
                    {
                        found = true;
                        break;
                    }
                    else if (!visited.contains(to))
                    {
                        queue.enqueue(to);
                    }
                }

                if (found)
                {
                    break;
                }
            }

            ret = found;
        }
    }

    return ret;
}
